/* Copyright 2009 Sony Mathew, Minneapolis MN. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at  http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
package com.xsm.gwt.widgets.client;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.xsm.lite.event.Event;
import com.xsm.lite.event.EventManager;
import com.xsm.lite.util.Destroyable;



/**
 * @author Sony Mathew
 * 
 * Represents a custom panel than can be built, destroyed and re-built on demand.
 * You must invoke build() to build the custom build the panel.
 *  You may also provide constuctors that take the build params and invokes build() explicitly.
 * Custom panels should extend this and add your custom widgets.
 */
public abstract class CustomPanel extends SimplePanel implements Event.Listener, Destroyable {
        
    public interface Context {
        public EventManager getEventManager();
    }

    private final Context cxt;    
    private int width = 0;
    private int height = 0;
    
    /**
     * author Sony Mathew
     */
    public CustomPanel(Context cxt) {
        this.cxt = cxt;
    }
        
    /**
     * Captures the width/height in pixels for later building.
     * Note: This is NOT setPixelSize() which sets the size of this panel explicitly and 
     *  will cause this panel to never change size again.
     * It is upto the build() method to decide whether setBuildSize() will equate exactly to setPixelSize().
     * The build() method may use the buildSize precisely or just as a suggestion or ignore it completely.
     */    
    public void setBuildSize(int width, int height) {
        this.width = width;
        this.height = height;
    }

    /**
     * @return int: height in pixels to build if set or 0 if none was set.
     * 
     * author Sony Mathew
     */
    public int getBuildHeight() {
        return height;
    }

    /**
     * @return int: width in pixels to build if set or 0 if none was set.
     * 
     * author Sony Mathew
     */
    public int getBuildWidth() {
        return width;
    }

    /**
     * Sublcasses should Build this custom panel within this method.
     * setBuildSize(width,height) beforehand - if you want this custom panel to be build to a specific width/height.
     * It is upto this CustomPanel to use the requested build size precisely or just as a suggestion or ignore it completely.
     * 
     * Important!
     * A CustomPanel may or may not be reusabe for build() after a destroy() 
     *  please consult its documentation as to whether they care re-built again.
     *  
     * By default does nothing.
     * 
     * author Sony Mathew
     */
    public abstract CustomPanel build();
    
    /**
     * Destroy this Panel and all its children and descendents.
     * 
     * Important!
     * Cannot override this method. Implement preDestruct() and postDestruct instead.
     * A CustomPanel may or may not be reusabe for build() after a destroy() 
     *  please consult its documentation as to whether they care re-built again.
     * 
     * @see #destroy(Widget)
     * author Sony Mathew
     */
    public final void destroy() {
        destroy(this);
    }
    
    /**
     * Called always before destruction of self initiates (if ever destroyed).
     *  Called before all its children have been destroyed.
     *  
     * By default does nothing.
     * 
     * author Sony Mathew
     */
    public void onPreDestruct() {        
    }

    /**
     * Called always after destruction of self completes (if ever destroyed).
     *  Called only after all its children have been destroyed.
     *  
     * By default does nothing.
     * 
     * author Sony Mathew
     */
    public void onPostDestruct() {        
    }

    /**
     * Called always before destruction of your children initiates.
     *  
     * By default does nothing.
     * 
     * author Sony Mathew
     */
    public void onPreDestructChildren() {        
    }

    /**
     * Called always after destruction of your children completes (if ever destroyed).
     *  Called only after all its children have been destroyed.
     *  
     * By default does nothing.
     * 
     * author Sony Mathew
     */
    public void onPostDestructChildren() {        
    }
    
    /**
     * Destroys the given widget including all its children and descendents.
     * 
     * Destruction includes: 
     *  Removing widget from its parent.
     *  De-registering from EventManager.     *  
     *  Destroying all children and descendents of the widget.
     * 
     * author Sony Mathew
     */
    protected void destroy(Widget w) {
        GWT.log("Destroying Widget [" + GWTUtil.toDebugString(w) + "]", null);        
        
        boolean isCustomPanel = (w instanceof CustomPanel);
        
        if (isCustomPanel) {
            ((CustomPanel)w).onPreDestruct();
        }
        
        try {
            if (w.getParent() != null) {
                w.removeFromParent();
            }
        }catch(Exception x) {
//          XXX: Some times fails for HTMLIncludePanel, not sure yet why.
            x.printStackTrace();
        }

        if (w instanceof Event.Listener) {
            cxt.getEventManager().removeListener((Event.Listener)w);            
        }
        
        if (w instanceof HasWidgets) {
            destroyChildren((HasWidgets)w);
        }
        
        if (isCustomPanel) {
            ((CustomPanel)w).onPostDestruct();
        }
    }

    /**
     * Destroys the children of the given Parent including all its descendents. 
     * cannot override, use onPreDestructChilren() & onPostDestructChilren() instead.
     */
    protected final void destroyChildren(HasWidgets parent) {
        boolean isCustomPanel = (parent instanceof CustomPanel);
        
        if (isCustomPanel) {
            ((CustomPanel)parent).onPreDestructChildren();
        }
        
        //collect all children into separate list first to prevent concurrent mods.
        List<Widget> childs = new ArrayList<Widget>();        
        Iterator<Widget> childIter = parent.iterator();
        while (childIter.hasNext()) {
            Widget child = childIter.next();
            childs.add(child);
        }     
        //destroy each child.
        for(Widget child : childs) {
            destroy(child);
        }
        
        if (isCustomPanel) {
            ((CustomPanel)parent).onPostDestructChildren();
        }        
    }
    
    /**
     * clears the children of this Panel and destroys each child and their descendents. 
     * If destroyChildren==false then it just removes the children as in original contract of Panel.clear().
     *
     * Caution: 
     * -------
     * Do NOT reference child/descendent widgets after calling this method.
     * 
     * Cannot override.
     * 
     * @see com.google.gwt.user.client.ui.Panel#clear()
     * 
     * author Sony Mathew
     */
    protected final void destroyChildren() {
        destroyChildren(this);
    }
        
    /**
     * Set the widget of this SimplePanel and if requested destroy the previous widget child
     *  including all its children and descendents. 
     * 
     * Caution: 
     * -------
     * Do NOT reference child/descendent widgets after calling this method with destroyChildren==true
     * 
     * Cannot override.
     *  
     * @see com.google.gwt.user.client.ui.SimplePanel#setWidget(com.google.gwt.user.client.ui.Widget)
     */
    public final void setWidget(Widget w, boolean destroyPreviousWidget) {
        if (getWidget() == w) {
            return; //nothing to do.
        }        
        destroyChildren();
        super.setWidget(w);
    }
    
    /**
     * Convenience method to fire an event using the EventManager.
     * 
     * author Sony Mathew
     */
    protected void fire(Event e) {
        cxt.getEventManager().fire(e);
    }
    
    /**
     * Convenience method to set the focus to the specified widget
     * @param focusable
     */
    public void focus(Focusable focusable) {
    	
    	final Focusable f = focusable;
		DeferredCommand.addCommand(new Command() {
			public void execute() {
				f.setFocus(true);
			}
		});
    	
    }
    
    /**
     * Convenience method to add myself as a Listener for the given eventClass.
     * You still need to implement the Event specific Listener.
     *  
     * author Sony Mathew
     */
    protected void listen(Class<? extends Event> eventClass) {
        cxt.getEventManager().addListener(eventClass, this);
    }
    
    /**
     * Indicate if this panel has been built.
     * If the panel has any children - it is presumed to be built (i.e. build() was invoked).
     * 
     * author Sony Mathew
     */
    public boolean isBuilt() {
        return this.getWidget() != null;
    }
        
    /**
     * A utility to check whether we have been built, if not then an IllegalStateException is thrown.
     * 
     * author Sony Mathew
     */
    public void verifyBuilt() {
        if (!isBuilt()) {
            throw new IllegalStateException("Expected to be built already");
        }        
    }
    
    /**
     * Shortcut to setting an AxisPanel as the content Widget of this SimplePanel.
     * Once the AxisPanel is set as the Widget content, it will NOT set it again until 
     *  a destroy() or clear() was done to clear the content widget.
     * 
     * You can choose to work with a different content panel altogether without ever invoking axis().
     * Just use setWidget() or add() directly.
     * 
     * Will result in a ClassCastException if there is a widget content already and is NOT an AxisPanel.
     *  
     * author Sony Mathew
     */
    protected AxisPanel axis() {
        if (getWidget() == null) {
            setWidget(new AxisPanel());
        }
        return (AxisPanel)getWidget();
    }
}
